<template>
    <div
        :style="CONTAINER_STYLE"
        class="flex-center sprite-container">
        <!--        你可以使用Transition创造出更多缓动效果哦！      -->
        <div
            :style="STYLE"
            class="sprite-obj">
            <slot></slot>
        </div>
    </div>
</template>

<script lang="ts" setup>
    import { ref, watch, onBeforeMount, onBeforeUnmount, type Ref } from 'vue';
    import type { SpriteRect } from './Types';
    import { transSize, onResize } from './transSizeMethods';
    import { getSpriteStyles } from './getSpriteStyles';

    // Props
    interface Props {
        spriteImage: string;
        sprites?: SpriteRect[]; // 精灵图集 用于做序列帧使用 传入对象数组，每个对象包含图片帧信息
        fit?: string; // 填充模式
        fps?: number; // 每秒钟的帧数
        loop?: number | boolean; // 循环次数 true一直循环，(false|1) 播放一次
        autoLast?: boolean; // 调用setIndex方法时，如果超过当前动画序列，则保持在最后一帧
        spriteWidth: number | string; // 精灵的宽度 仅支持[128(默认为px), 150px, 6rem] 形式传入
        spriteHeight: number | string; // 精灵的高度 仅支持[128, 150px, 6rem] 形式传入
    }

    const props = withDefaults(defineProps<Props>(), {
        spriteImage: '',
        sprites: () => [],
        fit: 'none',
        fps: 1,
        loop: true,
        autoLast: true,
        spriteWidth: 0,
        spriteHeight: 0,
    });

    // Emits
    const emit = defineEmits(['change', 'start', 'end', 'play', 'pause']);

    // loop times
    const loopTimes: Ref<number | string> = ref(0);

    // 单位转换
    const CONTAINER_STYLE = ref(``);
    const handleResize = () => {
        CONTAINER_STYLE.value = onResize(<string>props.spriteWidth, <string>props.spriteHeight);
        getStyle();
    };
    onBeforeMount((): void => {
        handleResize();
        loopTimes.value = getTimes();
        window.addEventListener('resize', () => {
            innerWidth.value = window.innerWidth;
            innerHeight.value = window.innerHeight;
        });
    });
    const innerWidth = ref(0);
    const innerHeight = ref(0);
    watch(() => innerHeight.value, handleResize);
    watch(() => innerWidth.value, handleResize);

    // 计算精灵样式
    const pxReference = ref(37.5);
    const stylesConfig = ref([]);
    // 循环次数 0 n infinite
    const getTimes = (): number | string => {
        let times;
        if (props.loop === true) {
            times = 'infinite';
        } else if (props.loop === false) {
            times = 1;
        } else {
            times = <number>props.loop;
        }
        return times;
    };
    const getStyle = () => {
        pxReference.value = window.innerWidth / 10;
        const containerWidth = transSize(<string>props.spriteWidth);
        const containerHeight = transSize(<string>props.spriteHeight);
        stylesConfig.value = getSpriteStyles(
            pxReference.value,
            props.spriteImage,
            props.sprites,
            containerWidth,
            containerHeight,
            props.fit
        );
        loopTimes.value = getTimes();
        onStart(0);
    };
    watch(() => props.fit, getStyle);
    watch(() => props.fps, getStyle);
    watch(() => props.loop, getStyle);

    const STYLE = ref(``);
    let spritesWatcher;
    onBeforeUnmount((): void => {
        clearInterval(spritesWatcher);
        spritesWatcher = undefined;
    });

    // 当前播放帧的索引
    const currentIndex = ref(0);
    /**
     * @function onStart
     * @description 进入开始播放的准备
     * @author: 邓子强
     * @Date: 2022/6/9
     * @param startIndex 开始的位置
     */
    const onStart = (startIndex?: number) => {
        emit('start');
        if (stylesConfig.value.length <= 1 || props.loop === 0) {
            emit('play');
            STYLE.value = stylesConfig.value?.[0];
            currentIndex.value = 0;
            emit('end');
            return;
        }
        // 当前帧
        if (startIndex !== undefined) {
            currentIndex.value = startIndex;
        }
        onPlay();
    };
    /**
     * @function onPlay
     * @description 进入开始播放的准备
     * @author: 邓子强
     * @Date: 2022/6/9
     */
    const onPlay = () => {
        emit('play');
        clearInterval(spritesWatcher);
        spritesWatcher = setInterval(() => {
            STYLE.value = stylesConfig.value[currentIndex.value];
            // 暴露当前所在的帧
            emit('change', currentIndex.value);
            currentIndex.value++;
            if (currentIndex.value >= stylesConfig.value.length) {
                currentIndex.value = 0;
                if (loopTimes.value !== 'infinite') {
                    // eslint-disable-next-line no-param-reassign
                    (<number>loopTimes.value)--;
                    if (loopTimes.value <= 0) {
                        clearInterval(spritesWatcher);
                        loopTimes.value = getTimes();
                        emit('end');
                    }
                }
            }
        }, 1000 / (props.fps || 1));
    };
    /**
     * @function onPause
     * @description 暂停
     * @author: 邓子强
     * @Date: 2022/6/9
     */
    const onPause = (): number => {
        clearInterval(spritesWatcher);
        emit('pause');
        return currentIndex.value;
    };

    /**
     * @function getIndex 获取当前播放帧的索引
     * @function setIndex 设置当前播放帧的索引
     * @author: 邓子强
     * @Date: 2022/6/9
     */
    const getIndex = () => currentIndex.value;
    const setIndex = (index) => {
        /* eslint-disable no-param-reassign */
        clearInterval(spritesWatcher);
        if (props.autoLast) {
            index = index >= stylesConfig.value.length ? stylesConfig.value.length - 1 : index;
        }
        if (index < 0) {
            index = 0;
        }
        STYLE.value = stylesConfig.value[index];
        currentIndex.value = index;
    };

    // 暴露方法
    defineExpose({
        refresh: getStyle,
        onPlay,
        onPause,
        getIndex,
        setIndex,
    });
</script>

<style lang="scss" scoped>
    .sprite-container {
        position: relative;
        flex: 0 0 auto;
        animation: none;
        overflow: hidden;
        z-index: 1;
    }

    .sprite-obj {
        flex: 0 0 auto;
        background-repeat: no-repeat;
    }
</style>
